#include <fs/devfs.h>

#include <mm/slab.h>

#include <status.h>
#include <log.h>

extern const f_ops devfs_file_ops;
extern const inode_ops devfs_inode_ops;

int devfs_mknod(dentry_t* dentry, device_t* dev) {
    assert(dev);
    inode_t* devnod = fs_find_inode(dentry->sb, dev->id);
    if (!devnod) {
        if ((devnod = fs_alloc_inode(dentry->sb))) {
            devnod->id = dev->id;
            devnod->data = dev;
            devnod->type = FS_FILE;

            fs_inode_addhash(devnod);
        } else {
            return ENOMEM;
        }
    }

    fs_assign_inode(dentry, devnod);
    return 0;
}

static int devfs_lookup(inode_t* inode, dentry_t* dentry) {
    device_t* dev = device_getbyhname((device_t*)inode->data, &dentry->name);

    if (!dev) {
        return ENOTDIR;
    }


    return devfs_mknod(dentry, dev);
}

static int devfs_write(inode_t* inode, void* buf, u32 len, u32 fpos) {
    assert(inode->data);
    device_t* dev = (device_t*)inode->data;

    if (!dev->write) {
        return ENOSET;
    }
    return dev->write(dev, buf, fpos, len);
}

static int devfs_read(inode_t* inode, void* buf, u32 len, u32 fpos) {
    assert(inode->data);
    device_t* dev = (device_t*)inode->data;

    if (!dev->read) {
        return ENOSET;
    }
    return dev->read(dev, buf, fpos, len);
}

void devfs_init_inode(sb_t* sb, inode_t* inode) {
    inode->iops = &devfs_inode_ops;
    inode->fops = &devfs_file_ops;
}

static int devfs_mount(sb_t* sb, dentry_t* mnt_point) {
    sb->init_inode = devfs_init_inode;

    inode_t* inode = fs_alloc_inode(sb);
    if (!inode) {
        return ENOMEM;
    }

    inode->id = -1;
    inode->type = FS_DIR;

    fs_assign_inode(mnt_point, inode);
    return 0;
}

static int devfs_umount(sb_t* sb) {
    return 0;
}

static u32 devfs_get_dtype(device_t* dev) {
    switch (dev->type & DEV_TYPE_MASK) {
    case DEV_CATE:
        return DT_DIR;
    default:
        return DT_FILE;
    }
}

int devfs_readdir(file_t* file, dirent_t* dirent) {
    device_t* dev = device_getbyoffset((device_t*)(file->inode->data), dirent->d_offset - 2);
    
    if (!dev) {
        return 0;
    }

    dirent_assign(dirent, dev->name.val, dev->name.len, devfs_get_dtype(dev));
    return 1;
}

void devfs_init() {
    fs_t* devfs = fs_alloc_fs("devfs", 5);
    fs_register(devfs);

    devfs->mount = devfs_mount;
    devfs->umount = devfs_umount;
}

const f_ops devfs_file_ops = {
    .close = default_file_close,
    .read = devfs_read,
    .write = devfs_write,
    .readdir = devfs_readdir
};

const inode_ops devfs_inode_ops = {
    .lookup = devfs_lookup,
    .mkdir = default_inode_mkdir,
    .rmdir = default_inode_rmdir,
    .open = default_inode_open
};